package apiserver

import (
	"context"
	"errors"
	"github.com/kataras/iris/v12"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	api "gluenet/pkg/apis"
	"gluenet/pkg/log/v1"
	"gluenet/pkg/nets"
	"gluenet/pkg/ops"
	"net"
	"strings"
)

const (
	server = api.Label("server")
)

var servers = map[api.Label]func(ctx context.Context, modLabel api.Label) api.GlueRunner{
	server: NewDefaultServer,
}

type ServerFactory struct {
	ctx context.Context

	servers map[api.Label]api.GlueRunner
	port    string

	db     *ops.DefaultDB
	nets   nets.GlueNets
	logger v1.Logger
}

func (r *ServerFactory) InitFlag(flags *pflag.FlagSet) {
	flags.String("server.port", "10000", "gluenet server port")
	r.db.InitFlag(flags)
	r.nets.InitFlag(flags)

	for _, v := range r.servers {
		v.InitFlag(flags)
	}
}

func (r *ServerFactory) ViperConfig(viper *viper.Viper) {
	for _, v := range r.servers {
		v.ViperConfig(viper)
	}
}

func (r *ServerFactory) InitViper(viper *viper.Viper) {
	r.port = viper.GetString("server.port")
	r.db.InitViper(viper)
	r.nets.InitViper(viper)
	for _, v := range r.servers {
		v.InitViper(viper)
	}
}

func (r *ServerFactory) Initialize() {
	r.logger = v1.NewLogger("runner")

	if err := r.db.Connect(); err != nil {
		r.logger.Fatalf("data base connect %v", err)
	}
	if err := r.nets.Connect(); err != nil {
		r.logger.Fatalf("nets connect %v", err)
	}
	app := iris.New()

	gCtx := api.GlueContext{
		Context:  r.ctx,
		DataBase: r.db,
		Rpc:      r.nets,
		Web:      app,
	}

	// initialize server
	for label, v := range r.servers {
		v.Initialize(v1.NewLogger(string(label)), &gCtx)
	}

	// run server
	if err := app.Run(iris.Addr(":" + r.port)); err != nil {
		if errors.Is(err, iris.ErrServerClosed) {
			r.logger.Infof("server closed")
		} else {
			r.logger.Infof("server error %v", err)
		}
	}

	// wait to close
	r.logger.Infof("server initialize finished")
	<-r.ctx.Done()
}

func (r *ServerFactory) Close() {
	for label, s := range r.servers {
		if err := s.Close(); err != nil {
			r.logger.Errorf("close %v %v", label, err)
		} else {
			r.logger.Infof("close %v succeed", label)
		}
	}

	if err := r.db.Close(); err != nil {
		r.logger.Errorf("close glue.impl %v", err)
	}
	if err := r.nets.Close(); err != nil {
		r.logger.Errorf("close glue.rpc %v", err)
	}
}

func NewServer(ctx context.Context) *ServerFactory {
	r := ServerFactory{
		ctx:     ctx,
		servers: make(map[api.Label]api.GlueRunner),

		db:   ops.NewDB(ctx, "glue.impl"),
		nets: nets.NewNets(ctx, "glue.rpc"),
	}

	for label, newFunc := range servers {
		r.servers[label] = newFunc(ctx, label)
	}
	return &r
}

func getIP() (ip string) {
	conn, _ := net.Dial("udp", "8.8.8.8:53")
	localAddr := conn.LocalAddr().(*net.UDPAddr)
	ip = strings.Split(localAddr.String(), ":")[0]
	return
}
